package com.hwtx.form.domain.def.meta;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.*;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.domain.service.ResourceService;
import com.hwtx.form.domain.vo.FormConfigQueryVo;
import com.hwtx.form.dto.FormDefDto;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.util.FormUtil;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.system.entity.SysMenu;
import io.geekidea.boot.system.enums.SysMenuType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.HTTP_GET;
import static com.hwtx.form.domain.FormConstants.OPERATION_HEADER;
import static com.hwtx.form.domain.def.meta.Component.Action.getReloadAction;
import static com.hwtx.form.domain.def.meta.Component.Form.buildForm;
import static com.hwtx.form.util.FormUtil.getFormAuthCodePrefix;

@EqualsAndHashCode(callSuper = true)
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Slf4j
public class Button extends Component {

    public static final String i18nAdd = "#i18n{add}";
    public static final String i18nEdit = "#i18n{edit}";

    private String actionType;
    private Dialog dialog;
    private Component drawer;
    private String icon;
    private String confirmText;
    private String confirmTitle;
    private String visibleOn;
    private String reload;
    private String size;
    private String level;
    private Api api;
    private Boolean blank;
    private String url;
    private OnEvent onEvent;

    public Button() {
        super("button");
    }

    public static String getTypeName() {
        return "button";
    }

    public static List<Component> buildListExtComponents(FormDefDetail formDefDetail, List<FormConfigQueryVo> formConfigQueryList, boolean crud, Set<String> applies) {
        return buildListExtComponents(formDefDetail, formConfigQueryList, null, crud, applies);
    }

    public static List<Component> buildListExtComponents(FormDefDetail formDefDetail, List<FormConfigQueryVo> formConfigQueryList, String reloadComponent, boolean crud, Set<String> applies) {
        List<Component> components = Lists.newArrayList();
        List<FormDetailExt.Operation> operations = FormUtil.getFormDetailOperationExt(formDefDetail.getExt());
        if (operations == null) {
            return Lists.newArrayList();
        }
        AtomicReference<FormConfigQueryVo> searchItem = new AtomicReference<>();
        boolean hasSearch = false;
        if (applies.equals(Set.of(OPERATION_HEADER))) {
            hasSearch = formConfigQueryList.stream().anyMatch(vo -> {
                FormConfigExt.ListConfig listConfig = FormUtil.getListConfig(vo.getExt());
                boolean ret = listConfig != null && listConfig.getSearch() != null;
                if (ret) {
                    searchItem.set(vo);
                }
                return ret;
            });
        }
        boolean hasOperationSearch = false;
        for (FormDetailExt.Operation operation : operations) {
            boolean apply = applies.equals(operation.getApplies());
            if (StringUtils.equals(operation.getValue(), FormConstants.OPERATION_ADD) && apply) {
                components.add(buildAddOrEditButton(formDefDetail, operation, formConfigQueryList, true, crud));
            } else if (StringUtils.equals(operation.getValue(), FormConstants.OPERATION_DELETE) && apply) {
                components.add(buildDeleteButton(formDefDetail, operation, reloadComponent));
            } else if (StringUtils.equals(operation.getValue(), FormConstants.OPERATION_EDIT) && apply) {
                components.add(buildAddOrEditButton(formDefDetail, operation, formConfigQueryList, false, crud));
            } else if (StringUtils.equals(operation.getValue(), FormConstants.OPERATION_DELETE_BULK) && apply) {
                components.add(buildBulkDeleteButton(formDefDetail));
            } else if (StringUtils.equals(operation.getValue(), FormConstants.OPERATION_SEARCH) && apply) {
                if (formConfigQueryList.isEmpty()) {
                    throw new RuntimeException("不存在可展示的表单项，formId = " + formDefDetail.getFormId());
                }
                FormConfigQueryVo formConfigQuery = formConfigQueryList.stream().filter(item -> {
                    FormConfigExt formConfigExt = FormUtil.getFormConfigExt(item.getExt());
                    if (formConfigExt != null && formConfigExt.getList() != null && formConfigExt.getList().getSearch() != null) {
                        return formConfigExt.getList().getSearch().getOperator() != null;
                    }
                    return false;
                }).findFirst().orElse(null);
                if (formConfigQuery != null) {
                    SearchBox searchBox = new SearchBox();
                    FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
                    if (formConfigExt != null) {
                        String alias = formConfigExt.getList().getSearch().getAlias();
                        if (StringUtils.isNotBlank(alias)) {
                            searchBox.setName(alias);
                        } else {
                            searchBox.setName(formConfigQuery.getField());
                        }
                        searchBox.setClearable(true);
                        if (Objects.equals(formDefDetail.getPageable(), (byte) 2)) {
                            SearchEvent searchEvent = new SearchEvent();
                            Action action = getReloadAction(reloadComponent, Map.of(searchBox.getName(), "${" + searchBox.getName() + "}"));
                            searchEvent.setActions(Lists.newArrayList(action));
                            OnEvent onEvent = new OnEvent();
                            onEvent.setSearch(searchEvent);
                            searchBox.setOnEvent(onEvent);
                        }
                        searchBox.setPlaceholder(formConfigQuery.getDescription());
                        if (!crud) {
                            Style style = new Style();
                            style.put("margin-right", 5);
                            style.put("margin-bottom", 5);
                            searchBox.setStyle(style);
                        }
                        components.add(searchBox);
                    }
                }
                hasOperationSearch = true;
            } else if (Objects.equals(FormConstants.OPERATION_TARGET_PAGE, operation.getValue()) && apply) {
                components.add(buildTargetPageButton(operation));
            } else if (Objects.equals(FormConstants.OPERATION_TARGET_ACTION, operation.getValue()) && apply) {
                components.add(buildActionButton(operation, reloadComponent));
            }
        }
        if (hasSearch && !hasOperationSearch) {
            throw new BusinessException("字段【" + searchItem.get().getField() + "】已配置请求搜索，请为列表添加搜索配置");
        }
        return components;
    }

    private static Button buildTargetPageButton(FormDetailExt.Operation operation) {

        FormRepo formRepo = SpringUtil.getApplicationContext().getBean(FormRepo.class);
        Button button = new Button();
        button.setLabel(operation.getLabel());
        button.setId(getComponentId());
        ServiceBody serviceBody = new ServiceBody(GenFormContextHolder.getDomain(), GenFormContextHolder.getContextPath());
        Long formId = formRepo.getFormIdByName(operation.getTarget());
        if (formId == null) {
            throw new RuntimeException("表单不存在,name = " + operation.getValue());
        }
        serviceBody.buildLoadSchema(formId);
        FormConfigRepo formConfigRepo = SpringUtil.getApplicationContext().getBean(FormConfigRepo.class);
        if (Objects.equals(Dialog.getTypeName(), operation.getPopType())) {
            button.setActionType(Dialog.getTypeName());
            Dialog dialog = new Dialog();
            dialog.setTitle(operation.getLabel());
            dialog.setBody(Lists.newArrayList(serviceBody));
            fillDialogSize(dialog, formConfigRepo.getFormDetail(formId));
            if (MapUtils.isNotEmpty(operation.getActionParams())) {
                dialog.setData(operation.getActionParams().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
            }
            button.setDialog(dialog);
        } else if (Objects.equals(Component.Page.getTypeName(), operation.getPopType())) {
            FormDef formDef = formRepo.getFormDef(formId);
            SysMenu sysMenu = new SysMenu();
            sysMenu.setCode(formDef.getName());
            sysMenu.setStatus(true);
            sysMenu.setType(SysMenuType.MENU.getCode());
            List<SysMenu> resources = SpringUtil.getBean(ResourceService.class).getResources(sysMenu);
            String url = "/#";
            if (!resources.isEmpty()) {
                url += resources.getFirst().getRouteUrl();
            } else {
                throw new BusinessException("弹出页面不存在或已停用，表单： " + formDef.getName());
            }
            StringBuilder builder = new StringBuilder();
            Map<String, String> paramMapping = operation.getActionParams();
            if (MapUtils.isNotEmpty(paramMapping)) {
                paramMapping.forEach((k, v) -> {
                    if (FormUtil.isVariable(v)) {
                        builder.append(k).append("=").append(v).append("&");
                    } else {
                        builder.append(k).append("=${").append(v).append("}").append("&");
                    }
                });
            }
            button.setActionType("url");
            if (!builder.isEmpty()) {
                builder.deleteCharAt(builder.length() - 1);
                url += "?" + builder;
            }
            button.setUrl(url);
            button.setBlank(false);
        } else {
            button.setActionType(Drawer.getTypeName());
            Drawer drawer = new Drawer();
            drawer.setLabel(operation.getLabel());
            drawer.setSize("lg");
            drawer.setBody(Lists.newArrayList(serviceBody));
            String popType = operation.getPopType();
            String[] position = popType.split("_");
            if (position.length == 2) {
                drawer.setPosition(position[0]);
            }
            if (MapUtils.isNotEmpty(operation.getActionParams())) {
                drawer.setData(operation.getActionParams().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
            }
            button.setDrawer(drawer);
        }
        return button;
    }

    private static Button buildBulkDeleteButton(FormDefDetail formDefDetail) {
        Button button = new Button();
        button.setLabel("#i18n{bat.delete}");
        button.setId(getComponentId());
        button.setActionType("ajax");
        button.setConfirmTitle("#i18n{delete}");
        button.setConfirmText("#i18n{sure.bat.delete}");
        StringBuilder param = new StringBuilder();
        Objects.requireNonNull(getBtnCondition(formDefDetail)).forEach((k, v) -> param.append(k).append("=").append(v).append("&"));
        button.setApi(Api.builder().method("post").url(GenFormContextHolder.getDomain() + GenFormContextHolder.getContextPath() + "/form/remove?valueIds=${ids|raw}&formId=" + formDefDetail.getFormId() + "&" + param).build());
        return button;
    }

    private static Button buildDeleteButton(FormDefDetail formDefDetail, FormDetailExt.Operation operation, String reloadComponent) {
        Button button = new Button();
        button.setActionType("ajax");
        button.setLabel("#i18n{delete}");
        button.setId(getComponentId());
        button.setConfirmText("#i18n{sure.delete}");
        button.setConfirmTitle("#i18n{delete}");
        handleVisible(operation, button);
        button.setReload(reloadComponent);
        StringBuilder param = new StringBuilder();
        Objects.requireNonNull(getBtnCondition(formDefDetail)).forEach((k, v) -> param.append(k).append("=").append(v).append("&"));
        button.setApi(Api.builder().method("post").url(GenFormContextHolder.getDomain() + GenFormContextHolder.getContextPath() + "/form/remove?valueIds=${id}&formId=" + formDefDetail.getFormId() + "&" + param).build());
        return button;
    }

    private static Button buildAddOrEditButton(FormDefDetail formDefDetail, FormDetailExt.Operation operation, List<FormConfigQueryVo> formConfigQueryList, boolean add, boolean crud) {
        if (formConfigQueryList.isEmpty()) {
            throw new RuntimeException("不存在可展示的表单项，formId = " + formDefDetail.getFormId());
        }
        Button button = new Button();
        button.setActionType(Dialog.getTypeName());
        button.setLabel(add ? i18nAdd : i18nEdit);
        if (add) {
            button.setIcon("fa fa-plus pull-left");
            if (!crud) {
                Style style = new Style();
                style.put("margin-right", 5);
                style.put("margin-bottom", 5);
                button.setStyle(style);
            }
        }
        button.setId(getFormAuthCodePrefix(GenFormContextHolder.getFormDef(), "") + (add ? ":add" : ":edit"));
        button.setAuthId((add ? "add" : "edit") + GenFormContextHolder.getFormId());
        Dialog dialog = new Dialog();
        dialog.setTitle(add ? i18nAdd : i18nEdit);
        fillDialogSize(dialog, formDefDetail);
        boolean needNewForm = Component.getValidateFormItemsWithoutHidden(formConfigQueryList, formDefDetail).size() > 4;
        if (!needNewForm) {
            Form form = buildForm(formDefDetail.getFormId(), new FormProperties.Meta(), formDefDetail,
                    Component.getValidateFormItems(formConfigQueryList), null, null, FormUtil.buildFormParams(formDefDetail));
            dialog.setBody(Lists.newArrayList(form));
            button.setDialog(dialog);
        } else {
            FormRepo formRepo = SpringUtil.getApplicationContext().getBean(FormRepo.class);
            Long formId;
            FormDef formDef = formRepo.getFormDefByName(formDefDetail.getKeyId() + "-form");
            if (formDef != null) {
                formId = formDef.getFormId();
            } else {
                formId = formRepo.genFormId(null);
            }
            if (formDef == null || StringUtils.isEmpty(formDef.getMeta().getGenBy()) || Objects.equals(formDef.getMeta().getGenBy(), GenFormContextHolder.getFormId().toString())) {
                try {
                    Form form = buildForm(formId, null, formDefDetail,
                            Component.getValidateFormItems(formConfigQueryList), formDefDetail.getFormId().toString(), formDefDetail.getFormId().toString(), FormUtil.buildFormParams(formDefDetail));
                    FormDefDto formDefDto = FormUtil.buildFormConfig(formDefDetail, form);
                    if (formId == null) {
                        formId = formRepo.addFormDef(formDefDto);
                    } else {
                        formDefDto.setId(formId);
                        formRepo.updateFormDef(formDefDto);
                    }
                } catch (Exception e) {
                    throw new BusinessException(e);
                }
            } else {
                log.info("表单 {} 非自动生成，请手动生成表单,操作：{}", formDef.getName(), add ? "新增" : "编辑");
            }
            Map<String, Object> data = Maps.newHashMap();
            if (operation.getActionParams() != null) {
                operation.getActionParams().forEach((k, v) -> {
                    if (StringUtils.isNotBlank(k)) {
                        data.put(k, v);
                    }
                });
            }
            dialog.setData(data);
            ServiceBody serviceBody = new ServiceBody(GenFormContextHolder.getDomain(), GenFormContextHolder.getContextPath());
            serviceBody.buildLoadSchema(formId);
            dialog.setBody(List.of(serviceBody));
            button.setDialog(dialog);
            if (!add) {
                handleVisible(operation, button);
            }
        }
        return button;
    }

    private static Map<String, String> getBtnCondition(FormDefDetail formDefDetail) {
        String ext = formDefDetail.getExt();
        Map<String, String> ret = Maps.newHashMap();
        List<FormDetailExt.Operation> operations = FormUtil.getFormDetailOperationExt(ext);
        if (CollectionUtils.isNotEmpty(operations)) {
            operations.forEach(op -> {
                if (CollectionUtils.isNotEmpty(op.getConditions())) {
                    op.getConditions().forEach(cond -> cond.getOpValues().forEach(opValue -> {
                        if (opValue.isVariable()) {
                            ret.put(opValue.getVar(), opValue.getValue());
                        }
                    }));
                }
            });
        }
        return ret;
    }

    private static void fillDialogSize(Dialog dialog, FormDefDetail formDefDetail) {
        if (StringUtils.isEmpty(formDefDetail.getWidth())) {
            return;
        }
        FixSize fixSize = FixSize.fromSize(formDefDetail.getWidth());
        if (fixSize != null) {
            dialog.setSize(fixSize.getSize());
        } else {
            dialog.setSize("custom");
            Style style = new Style();
            style.put("width", formDefDetail.getWidth() + "px");
            if (formDefDetail.getHeight() != null) {
                style.put("height", formDefDetail.getHeight() + "px");
                dialog.setBodyClassName("overflow-y-auto");
            }
            dialog.setStyle(style);
        }
    }

    private static Button buildActionButton(FormDetailExt.Operation operation, String reloadComponent) {
        Button button = new Button();
        button.setLabel(operation.getLabel());
        button.setId(getComponentId());
        button.setActionType("ajax");
        button.setConfirmText("#i18n{sure.action}");
        button.setConfirmTitle("#i18n{action}");
        StringBuilder api = FormUtil.getChainApi(GenFormContextHolder.getDomain(), GenFormContextHolder.getContextPath(), operation.getActionChain());
        if (MapUtils.isNotEmpty(operation.getActionParams())) {
            api.append("?");
            operation.getActionParams().forEach((key, value) -> api.append(key).append("=").append(value).append("&"));
        }
        handleVisible(operation, button);
        button.setApi(Api.builder().method(HTTP_GET).url(api.toString()).build());
        button.setReload(reloadComponent);
        return button;
    }

    private static void handleVisible(FormDetailExt.Operation operation, Button button) {
        StringBuilder visibleOn = new StringBuilder();
        if (CollectionUtils.isEmpty(operation.getVisible())) {
            return;
        }
        operation.getVisible().forEach(visible -> {
            String visibleType = visible.getVisibleType();
            if (StringUtils.isNotBlank(visibleType)) {
                if (Objects.equals(visibleType, "field")) {
                    String visibleField = visible.getVisibleField();
                    String visibleFieldOp = visible.getVisibleFieldOp();
                    String visibleFieldVal = visible.getVisibleFieldVal();
                    if (StringUtils.isNotBlank(visibleField) && StringUtils.isNotBlank(visibleFieldOp) && StringUtils.isNotBlank(visibleFieldVal)) {
                        visibleOn.append(visibleField).append(visibleFieldOp).append(visibleFieldVal).append("&&");
                    }
                } else if (Objects.equals(visibleType, "expr")) {
                    visibleOn.append(visible.getVisibleExpr()).append("&&");
                }
            }
        });
        if (!visibleOn.isEmpty()) {
            visibleOn.setLength(visibleOn.length() - 2);
        }
        button.setVisibleOn("${" + visibleOn + "}");
    }
}